package main.java.LeetCode.ClassicalAlgorithm.Queue.Impl;

import main.java.LeetCode.ClassicalAlgorithm.Queue.Queue;

import java.util.ArrayList;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

public class DynamicBlockQueue<E> implements Queue<E> {

    private int size;
    private int capacity;
    private final AtomicInteger count = new AtomicInteger(0);
    private final ThreadLocal<Integer> take;
    private final ThreadLocal<Integer> offer;
    private final ArrayList<LinkedBlockQueue<E>> group = new ArrayList<>();
    private final ReentrantLock mainLock = new ReentrantLock();

    private final Condition isEmpty = mainLock.newCondition();
    private final Condition isFull = mainLock.newCondition();


    public DynamicBlockQueue(){
        size = 0;
        capacity = 0;
        take = new ThreadLocal<>();
        offer = new ThreadLocal<>();
    }

    public synchronized DynamicBlockQueue<E> addQueue(LinkedBlockQueue<E> queue){
        capacity = capacity + queue.size();
        group.add(queue);
        size = group.size();
        return this;
    }

    private void LockForEmpty() throws InterruptedException {
        mainLock.lock();
        isFull.signal();
        isEmpty.await();
        mainLock.unlock();
    }

    private boolean LockForEmpty(long timeout,TimeUnit unit) throws InterruptedException {
        mainLock.lock();
        timeout = unit.toNanos(timeout);
        timeout = isEmpty.awaitNanos(timeout);
        mainLock.unlock();
        return timeout<0;
    }

    private void LockForFull() throws InterruptedException {
        mainLock.lock();
        isEmpty.signal();
        isFull.await();
        mainLock.unlock();
    }

    @Override
    public E get() throws InterruptedException {

//        while (count.get()<=0)
//            LockForEmpty();

        int index = take.get()==null? 0 : take.get();

        E e;
        int loop = 0;

        while ((e=group.get(index%size).take())==null){
            index++;
            if(index==Integer.MAX_VALUE)
                index = 0;
            loop++;
            if(loop==size+1){
                LockForEmpty();
                loop = 0;
            }
        }

        take.set(index+1);

        mainLock.lock();
        try {
            count.getAndDecrement();
            isFull.signal();
        }finally {
            mainLock.unlock();
        }

        return e;

    }

    @Override
    public boolean set(E e) throws InterruptedException {

//        while (count.get()>=capacity)
//            LockForFull(1);

        int index = offer.get()==null?0:offer.get();

        int loop = 0;

        while (!group.get(index%size).offer(e)){
            index++;
            if(index==Integer.MAX_VALUE)
                index = 0;
            loop++;

            if(loop==size+1){
                LockForFull();
                loop = 0;
            }

        }
        offer.set(index+1);

        mainLock.lock();
        try {
            count.getAndIncrement();
            isEmpty.signal();
        }finally {
            mainLock.unlock();
        }


        return true;
    }

    @Override
    public E get(long timeout, TimeUnit unit) throws InterruptedException {
        E e = null;
//        while (count.get()<=0) {
//            if(LockForEmpty(timeout,unit))
//                return e;
//        }

        int index = take.get()==null? 0 : take.get();

        int loop = 0;

        while ((e=group.get(index%size).take())==null){
            index++;
            if(index==Integer.MAX_VALUE)
                index = 0;
            loop++;
            if(loop==size+1){
                if(LockForEmpty(timeout,unit))
                    return e;
                loop = 0;
            }
        }

        take.set(index+1);

        mainLock.lock();
        try {
            count.getAndDecrement();
            isFull.signal();
        }finally {
            mainLock.unlock();
        }

        return e;
    }

    @Override
    public boolean offer(E e) {
        if (count.get()>=capacity) return false;

        int index = offer.get()==null?0:offer.get();

        int loop = 0;

        while (!group.get(index%size).offer(e)){
            index++;
            loop++;
            if(loop==size) return false;
        }
        offer.set(index+1);

        mainLock.lock();
        try {
            count.getAndIncrement();
            isEmpty.signal();
        }finally {
            mainLock.unlock();
        }

        return true;
    }

    @Override
    public int size() {
        return capacity;
    }

}
